/*
 * TODO language 
 * Copyright (c) 2010 TODO Team
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <iostream>
#include <sstream>
#include "numeric.h"

#define NUMERIC_VAL_OP(OP) \
	if (!value->type->is_child_of(typeman.get_type("numeric"))) \
		throw todo::Exception("cannot operate on non numeric object."); \
\
	Value* val = value->type->copy(value);\
	Value* arg = ARG(0);\
	bool mustfree = false;\
\
	const std::string& val_type = value->type->get_name();\
	const std::string& arg_type = arg->type->get_name();\
\
	if (val_type == "int") {\
		if (arg_type == "double") {\
			arg = ARG(0)->type->call_method("to_int", ARG(0), NULL, "");\
			mustfree = true;\
		}\
		INT_V(val) = INT_V(val) OP INT_V(arg);\
	}\
	else if (val_type == "double") {\
		if (arg_type == "int") {\
			arg = ARG(0)->type->call_method("to_double", ARG(0), NULL, "");\
			mustfree = true;\
		}\
		DOUBLE_V(val) = DOUBLE_V(val) OP DOUBLE_V(arg);\
	}\
\
	if (mustfree && arg) {\
		arg->type->dealloc(arg);\
	}\
\
	return val

#define NUMERIC_VAL_INT_OP(OP) \
	if (!value->type->is_child_of(typeman.get_type("numeric"))) \
		throw todo::Exception("cannot operate on non numeric object."); \
\
	Value* val = value->type->copy(value);\
	Value* arg = ARG(0);\
	bool mustfree = false;\
\
	const std::string& val_type = value->type->get_name();\
	const std::string& arg_type = arg->type->get_name();\
\
	if (arg_type == "double") {\
		arg = ARG(0)->type->call_method("to_int", ARG(0), NULL, "");\
		mustfree = true;\
	}\
\
	if (val_type == "double") {\
		val->type->dealloc(val); \
		val = value->type->call_method("to_int", value, NULL, "");\
	}\
	INT_V(val) = INT_V(val) OP INT_V(arg);\
\
	if (mustfree && arg) {\
		arg->type->dealloc(arg);\
	}\
\
	return val

#define NUMERIC_VAL_LOGIC_OP(OP) \
	Value *ret = NEW("bool"); \
	Value *arg = ARG(0); \
	const std::string& val_type = value->type->get_name(); \
	const std::string& arg_type = ARG(0)->type->get_name(); \
\
	bool mustfree = false; \
 \
	if (val_type == "int") {\
		if (arg_type == "double") {\
			arg = arg->type->call_method("to_int", arg, NULL, "");\
			mustfree = true;\
		}\
\
		BOOL_V(ret) = INT_V(value) OP INT_V(arg);\
	} else if (val_type == "double") {\
		if (arg_type == "int") {\
			arg = arg->type->call_method("to_double", arg, NULL, "");\
			mustfree = true;\
		}\
\
		BOOL_V(ret) = DOUBLE_V(value) OP DOUBLE_V(arg);\
	}\
		\
	if (mustfree)\
		arg->type->dealloc(arg);\
\
	return ret


namespace todo { namespace type {

Numeric::Numeric() : Type("numeric", TYPE_P("object"), true)
{
}

void Numeric::load(void)
{
	register_method(new todo::Method(TODO_PUBLIC, this, "__plus__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::plus));
	register_method(new todo::Method(TODO_PUBLIC, this, "__minus__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::minus));
	register_method(new todo::Method(TODO_PUBLIC, this, "__div__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::div));
	register_method(new todo::Method(TODO_PUBLIC, this, "__mult__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::mult));
	register_method(new todo::Method(TODO_PUBLIC, this, "__mod__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::mod));
	register_method(new todo::Method(TODO_PUBLIC, this, "__equal__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::equal));
	register_method(new todo::Method(TODO_PUBLIC, this, "__not_equal__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::not_equal));
	register_method(new todo::Method(TODO_PUBLIC, this, "__ge__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::greater));
	register_method(new todo::Method(TODO_PUBLIC, this, "__le__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::lower));
	register_method(new todo::Method(TODO_PUBLIC, this, "__gt__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::greater_than));
	register_method(new todo::Method(TODO_PUBLIC, this, "__lt__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::lower_than));
	register_method(new todo::Method(TODO_PUBLIC, this, "__sr__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::shift_right));
	register_method(new todo::Method(TODO_PUBLIC, this, "__sl__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::shift_left));
	register_method(new todo::Method(TODO_PUBLIC, this, "__bw_or__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::bitwise_or));
	register_method(new todo::Method(TODO_PUBLIC, this, "__bw_and__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::bitwise_and));
	register_method(new todo::Method(TODO_PUBLIC, this, "__xor__", 1, type_list(1, "__this__"), (MethodPtr)&Numeric::bitwise_xor));
	register_method(new todo::Method(TODO_PUBLIC, this, "__pre_inc__", 0, NULL, (MethodPtr)&Numeric::pre_increment));
	register_method(new todo::Method(TODO_PUBLIC, this, "__pre_dec__", 0, NULL, (MethodPtr)&Numeric::pre_decrement));
	register_method(new todo::Method(TODO_PUBLIC, this, "__pos_inc__", 0, NULL, (MethodPtr)&Numeric::pos_increment));
	register_method(new todo::Method(TODO_PUBLIC, this, "__pos_dec__", 0, NULL, (MethodPtr)&Numeric::pos_decrement));
	register_method(new todo::Method(TODO_PUBLIC, "int", "to_int", 0, NULL, (MethodPtr)&Numeric::to_int));
	register_method(new todo::Method(TODO_PUBLIC, "double", "to_double", 0, NULL, (MethodPtr)&Numeric::to_double));
}

TODO_METHOD(Numeric, plus)
{
	NUMERIC_VAL_OP(+);
}

TODO_METHOD(Numeric, minus)
{
	NUMERIC_VAL_OP(-);
}

TODO_METHOD(Numeric, div)
{
	NUMERIC_VAL_OP(/);
}

TODO_METHOD(Numeric, mult)
{
	NUMERIC_VAL_OP(*);
}

TODO_METHOD(Numeric, mod)
{
	NUMERIC_VAL_INT_OP(%);
}

TODO_METHOD(Numeric, equal)
{
	NUMERIC_VAL_LOGIC_OP(==);
}

TODO_METHOD(Numeric, not_equal)
{
	NUMERIC_VAL_LOGIC_OP(!=);
}

TODO_METHOD(Numeric, greater)
{
	NUMERIC_VAL_LOGIC_OP(>=);
}

TODO_METHOD(Numeric, lower)
{
	NUMERIC_VAL_LOGIC_OP(<=);
}

TODO_METHOD(Numeric, greater_than)
{
	NUMERIC_VAL_LOGIC_OP(>);
}

TODO_METHOD(Numeric, lower_than)
{
	NUMERIC_VAL_LOGIC_OP(<);
}

TODO_METHOD(Numeric, shift_left)
{
	NUMERIC_VAL_INT_OP(<<);
}

TODO_METHOD(Numeric, shift_right)
{
	NUMERIC_VAL_INT_OP(>>);
}

TODO_METHOD(Numeric, bitwise_or)
{
	NUMERIC_VAL_INT_OP(|);
}

TODO_METHOD(Numeric, bitwise_and)
{
	NUMERIC_VAL_INT_OP(&);
}

TODO_METHOD(Numeric, bitwise_xor)
{
	NUMERIC_VAL_INT_OP(^);
}

TODO_METHOD(Numeric, pre_increment)
{
	const std::string& val_type = value->type->get_name();

	if (val_type == "int")
		++INT_V(value);
	else if (val_type == "double")
		++DOUBLE_V(value);

	return value->type->copy(value);
}

TODO_METHOD(Numeric, pre_decrement)
{
	const std::string& val_type = value->type->get_name();

	if (val_type == "int")
		--INT_V(value);
	else if (val_type == "double")
		--DOUBLE_V(value);

	return value->type->copy(value);
}

TODO_METHOD(Numeric, pos_increment)
{
	const std::string& val_type = value->type->get_name();
	Value* val = value->type->copy(value);

	if (val_type == "int")
		++INT_V(value);
	else if (val_type == "double")
		++DOUBLE_V(value);

	return val;
}

TODO_METHOD(Numeric, pos_decrement)
{
	const std::string& val_type = value->type->get_name();
	Value* val = value->type->copy(value);

	if (val_type == "int")
		--INT_V(value);
	else if (val_type == "double")
		--DOUBLE_V(value);

	return val;
}

TODO_METHOD(Numeric, to_int)
{
	if (value->type->get_name() == "double") {
		Value* ret = NEW("int");
		INT_V(ret) = int(DOUBLE_V(value));
		return ret;
	}

	return copy(value); // integer.
}

TODO_METHOD(Numeric, to_double)
{
	if (value->type->get_name() == "int") {
		Value* ret = NEW("double");
		DOUBLE_V(ret) = double(INT_V(value));
		return ret;
	}

	return copy(value); // double.
}

TODO_METHOD(Numeric, toString)
{
	return value->type->call_method("toString", value, NULL, "");
}

Value* Numeric::copy(const Value* value)
{
	return value->type->copy(value);
}

Value* Numeric::alloc()
{
	return NULL;
}

void Numeric::dealloc(Value* value)
{
}

}}
